module Ruboto
  module Util
    module Build
      include Verify
      SCRIPTS_DIR = 'src'

      ###########################################################################
      #
      # Build Subclass or Interface:
      #

      #
      # build_file: Reads the src from the appropriate location,
      #   uses the substitutions hash to modify the contents,
      #   and writes to the new location
      #
      def build_file(src, package, name, substitutions, dest='.')
        to = File.join(dest, "src/#{package.gsub('.', '/')}")
        Dir.mkdir(to) unless File.directory?(to)

        text = File.read(File.expand_path(Ruboto::GEM_ROOT + "/assets/src/#{src}.java"))
        substitutions.each { |k, v| text.gsub!(k, v) }

        File.open(File.join(to, "#{name}.java"), 'w') { |f| f << text }
      end

      #
      # get_class_or_interface: Opens the xml file and locates the specified class.
      #   Aborts if the class is not found or if it is not available for
      #   all api levels
      #
      def get_class_or_interface(klass, force=nil)
        element = verify_api.find_class_or_interface(klass, 'either')

        abort "ERROR: #{klass} not found" unless element

        unless force == 'include'
          abort "#{klass} not available in minSdkVersion, added in #{element.attribute('api_added')}; use '--force include' to create it" if element.attribute('api_added') and element.attribute('api_added').to_i > verify_min_sdk.to_i
          abort "#{klass} deprecated for targetSdkVersion, deprecated in #{element.attribute('deprecated')}; use '--force include' to create it" if element.attribute('deprecated') and element.attribute('deprecated').to_i <= verify_target_sdk.to_i
        end

        abort "#{klass} removed for targetSdkVersion, removed in #{element.attribute('api_removed')}" if element.attribute('api_removed') and element.attribute('api_removed').to_i <= verify_target_sdk.to_i

        element
      end

      #
      # check_methods: Checks the methods to see if they are available for all api levels
      #
      def check_methods(methods, force=nil)
        min_api = verify_min_sdk.to_i
        target_api = verify_target_sdk.to_i

        # Remove methods changed outside of the scope of the sdk versions
        methods = methods.select { |i| !i.attribute('api_added') || (i.attribute('api_added').to_i <= target_api) }
        # methods = methods.select{|i| !i.attribute('deprecated') || (i.attribute('deprecated').to_i > min_api)}
        methods = methods.select { |i| !i.attribute('api_removed') || (i.attribute('api_removed').to_i > min_api) }

        # Inform and remove methods that do not exist in one of the sdk versions
        methods = methods.select do |i|
          if i.attribute('api_removed') and i.attribute('api_removed').to_i <= target_api
            puts "Can't create #{i.method_signature} -- removed in #{i.attribute('api_removed')}"
            false
          else
            true
          end
        end

        abort = false
        new_methods = methods
        unless force == 'include'
          # Inform and remove methods changed inside the scope of the sdk versions
          new_methods = methods.select do |i|
            if i.attribute('api_added') and i.attribute('api_added').to_i > min_api and force == 'exclude'
              false
            elsif i.attribute('api_added') and i.attribute('api_added').to_i > min_api
              puts "Can't create #{i.method_signature} -- added in #{i.attribute('api_added')} -- use method_exclude or force exclude"
              abort = true
              false
            elsif i.attribute('deprecated') and i.attribute('deprecated').to_i <= target_api and force == 'exclude'
              false
            elsif i.attribute('deprecated') and i.attribute('deprecated').to_i <= target_api
              puts "Can't create #{i.method_signature} -- deprecated in #{i.attribute('deprecated')} -- use method_exclude or force exclude"
              abort = true
              false
            else
              true
            end
          end

          abort('Aborting!') if abort
        end

        new_methods
      end

      #
      # generate_subclass_or_interface: Creates a subclass or interface based on the specifications.
      #
      def generate_subclass_or_interface(params)
        defaults = {:template => 'InheritingClass', :method_base => 'all', :method_include => '', :method_exclude => '', :force => nil, :implements => ''}
        params = defaults.merge(params)
        params[:package] ||= verify_package

        class_desc = get_class_or_interface(params[:class] || params[:interface], params[:force])

        print "Generating methods for #{params[:name]}..."
        methods = class_desc.all_methods(params[:method_base], params[:method_include], params[:method_exclude], params[:implements])
        methods = check_methods(methods, params[:force])
        puts "Done. Methods created: #{methods.count}"

        params[:implements] = params[:implements].split(',').push('org.ruboto.RubotoComponent').join(',')

        action = class_desc.name == 'class' ? 'extends' : 'implements'
        build_file params[:template], params[:package], params[:name], {
            'THE_METHOD_BASE' => params[:method_base].to_s,
            'THE_PACKAGE' => params[:package],
            'THE_ACTION' => action,
            'THE_ANDROID_CLASS' => (params[:class] || params[:interface]) +
                (params[:implements] == '' ? '' : ((action != 'implements' ? ' implements ' : ', ') + params[:implements].split(',').join(', '))),
            'THE_RUBOTO_CLASS' => params[:name],
            'THE_CONSTRUCTORS' => class_desc.name == 'class' ?
                class_desc.get_elements('constructor').map { |i| i.constructor_definition(params[:name]) }.join("\n\n") : '',
            'THE_METHODS' => methods_header + methods.map { |i| i.method_definition(params[:name]) }.join("\n\n")
        }
      end

      #
      # generate_core_classe: generates RubotoActivity, RubotoService, etc. based
      #   on the API specifications.
      #
      def generate_core_classes(params)
        hash = {:package => 'org.ruboto'}
        %w(method_base method_include implements force).inject(hash) { |h, i| h[i.to_sym] = params[i.to_sym]; h }
        hash[:method_exclude] = params[:method_exclude]

        %w(android.app.Activity android.app.Service android.content.BroadcastReceiver).each do |i|
          name = i.split('.')[-1]
          if params[:class] == name or params[:class] == 'all'
            generate_subclass_or_interface(hash.merge({:template => "Ruboto#{name}", :class => i, :name => "Ruboto#{name}"}))
          end
        end

        # Activities that can be created, but only directly  (i.e., not included in all)
        %w(android.preference.PreferenceActivity android.app.TabActivity).each do |i|
          name = i.split('.')[-1]
          if params[:class] == name
            generate_subclass_or_interface(hash.merge({:template => 'RubotoActivity', :class => i, :name => "Ruboto#{name}"}))
          end
        end
      end

      ###########################################################################
      #
      # generate_inheriting_file:
      #   Builds a script based subclass of Activity, Service, or BroadcastReceiver
      #
      def generate_inheriting_file(klass, name, package = verify_package, script_name = "#{underscore(name)}.rb")
        dest = '.'
        file = File.expand_path File.join(dest, "src/#{package.gsub('.', '/')}", "#{name}.java")
        text = File.read(File.join(Ruboto::ASSETS, "src/Inheriting#{klass}.java"))
        file_existed = File.exists?(file)
        File.open(file, 'w') do |f|
          f << text.gsub('THE_PACKAGE', package).gsub("Sample#{klass}", name).gsub("Inheriting#{klass}", name).gsub("sample_#{underscore(klass)}.rb", script_name)
        end
        puts "#{file_existed ? 'Updated' : 'Added'} file #{file}."

        script_file = File.expand_path("#{SCRIPTS_DIR}/#{script_name}", dest)
        unless File.exists? script_file
          sample_source = File.read(File.join(Ruboto::ASSETS, "samples/sample_#{underscore klass}.rb"))
          sample_source.gsub!('THE_PACKAGE', package)
          sample_source.gsub!("Sample#{klass}", name)
          sample_source.gsub!('start.rb', script_name)
          FileUtils.mkdir_p File.join(dest, SCRIPTS_DIR)
          File.open script_file, 'a' do |f|
            f << sample_source
          end
          puts "Added file #{script_file}."
        end

        test_file = File.expand_path("test/src/#{script_name.chomp('.rb')}_test.rb", dest)
        unless File.exists? test_file
          sample_test_source = File.read(File.join(Ruboto::ASSETS, "samples/sample_#{underscore klass}_test.rb"))
          sample_test_source.gsub!('THE_PACKAGE', package)
          sample_test_source.gsub!("Sample#{klass}", name)
          sample_test_source.gsub!('SampleActivity', verify_activity)
          File.open test_file, 'a' do |f|
            f << sample_test_source
          end
          puts "Added file #{test_file}."
        end
      end

      def methods_header
        <<EOF
    private final ScriptInfo scriptInfo = new ScriptInfo();
    public ScriptInfo getScriptInfo() {
        return scriptInfo;
    }

    /****************************************************************************************
     *
     *  Generated Methods
     */

EOF
      end

    end
  end
end
